/**
 * Copyright (c) 2013, Andrea Funto'. All rights reserved.
 * 
 * This file is part of the Object Graph Traversal Language framework ("OGTL").
 *
 * "OGTL" is free software: you can redistribute it and/or modify it under 
 * the terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 *
 * "OGTL" is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License 
 * along with "OGTL". If not, see <http://www.gnu.org/licenses/>.
 */
package org.dihedron.ogtl;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.dihedron.ogtl.compiler.Expression;
import org.dihedron.ogtl.compiler.ExpressionCompiler;
import org.dihedron.ogtl.exceptions.ExpressionException;
import org.dihedron.ogtl.exceptions.LexerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class simplifies object traversal through an easy-to-use API: an 
 * {@code ObjectWalker} can be used to compile an expression on the fly and 
 * apply it to the given target object; moreover the API provides two different
 * mechanisms to optimise properties access: it gives the opportunity to reuse
 * {@code ObjectWalker}s for several instances of the same class, and each
 * {@code ObjectWalker} stores a reference to all compiled expressions, in order
 * to restrict compilation to new expressions only. 
 * 
 * @author Andrea Funto'
 */
public class ObjectWalker<T> {
	
	/**
	 * The logger.
	 */
	private final static Logger logger = LoggerFactory.getLogger(ObjectWalker.class);

	/**
	 * A collection of object walkers, one for each possible class to traverse. 
	 * These instances can be shared among thread and will keep track of expressions
	 * in order to minimise expression compilation time.
	 */
	private static Map<Class<?>, ObjectWalker<?>> walkers = 
			Collections.synchronizedMap( new HashMap<Class<?>, ObjectWalker<?>>() );
	
	@SuppressWarnings("unchecked")
	public static <T> ObjectWalker<T> getObjectWalker(Class<T> clazz) {
		if(!walkers.containsKey(clazz)) {
			walkers.put(clazz, new ObjectWalker<T>());
			logger.debug("creating ObjectWalker for class '{}'", clazz.getSimpleName());
		}
		return (ObjectWalker<T>)walkers.get(clazz);
	}
	
	/**
	 * A map of input expression (as strings) to compiled {@code Expression}
	 * objects; this will be populated as expressions get evaluated, in order to
	 * boost access performance.
	 */
	private Map<String, Expression> expressionsCache;
	
	/**
	 * Constructor.
	 */
	public ObjectWalker() {
		expressionsCache = Collections.synchronizedMap(new HashMap<String, Expression>());
	}
	
	/**
	 * Applies the given expression to the target object.
	 * 
	 * @param object
	 *   the object to which the expression will be applied.
	 * @param expression
	 *   the expression to be applied; if not present in the internal cache, it
	 *   will be compiled into executable form on the fly and then and stored 
	 *   for future reference.
	 * @return
	 *   the result of the expression application.
	 * @throws LexerException
	 *   if the expression could not be parsed or compiled.
	 * @throws ExpressionException
	 *   if the expression could not be applied, e.g. due to missing properties
	 *   in the target object.
	 */
	public Object get(T object, String expression) throws LexerException, ExpressionException {
		if(!expressionsCache.containsKey(expression)) {
			expressionsCache.put(expression, ExpressionCompiler.getExpressionCompiler().compile(expression));
		}
		return expressionsCache.get(expression).apply(object);
	}
}
